// -*- mode:C; tab-width:2; indent-tabs-mode:nil;  -*-

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>

#include <glib.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>

#include "launchpadData.h"
#include "hashTable.h"
#include "eventManager.h"
#include "factory.h"

#define NWORKERS 5

hashTable_t h;

hashTable_t h_label;

EXPOSETYPE(onData_t)
INSTANTIATETYPE(onData_t, char* eventName;
													char* startDirectory;
                          char* cmd;
                          char** args;)
CREATEMANAGER( onData_t)


EXPOSETYPE(fireData_t)
INSTANTIATETYPE(fireData_t, int pid; )
CREATEMANAGER( fireData_t )

EXPOSETYPE(callbackData_t)
INSTANTIATETYPE(	callbackData_t,
					 	char* eventName;
						int numberActive;
						eventManager_t manager;
						launchpad_data_t buttonData;)
						
CREATEMANAGER(callbackData_t)



// Updates the number of active worker threads by numToIncrement(pass in negative to decrement)
void active_number_update(eventName_t eventName, int numToIncrement) {
  int *v = (int*) malloc(sizeof(int));  
  char *k = (char*) malloc(sizeof(char)*strlen(eventName));
  int *remaining_value = (int*) malloc(sizeof(int));


  strcpy(k, eventName);    // copy event name over
  *v = *((int *)lookupHashItem(h, eventName));  //look up in our hashtable
  *remaining_value = *((int *)lookupHashItem(h, "launch_count"));
  *v = *v + numToIncrement;
  *remaining_value = *remaining_value - numToIncrement;
  addHashItem(h, k, v, free, free);  //replace and free in the table!
  addHashItem(h, "launch_count", remaining_value, NULL, free);
}


void label_number_update(eventName_t eventName) {

	GtkLabel *label = (GtkLabel*)lookupHashItem(h_label, eventName);    //Create label
  GtkLabel *remaining_label = (GtkLabel*)lookupHashItem(h_label, "launch_count");
  char buffer[10]; 
  char rbuffer[10];
  int v, remaining_value;  
  v = *((int *)lookupHashItem(h, eventName));   
  remaining_value = *((int *)lookupHashItem(h, "launch_count"));   
	sprintf( buffer, "%d active", v);
  sprintf( rbuffer, "%d", remaining_value);

	gdk_threads_enter();
 	gtk_label_set_text(label,buffer);  //set the label to whatever is in buffer
 	gtk_label_set_text(remaining_label,rbuffer);  //set the label to whatever is in buffer
	gdk_threads_leave();
}

void *active_label_thread(void *args) {

	int button_count = getNButtons();
	launchpad_data_t** button_data = getButtons();
	while (1) {
	 usleep(100000);
	 int i;
 	 for(i=0; i < button_count; i++){
				label_number_update(button_data[i]->buttonLabel);
		}
	}
}


void onDataDestructor( onData_t onData ) {
		if (onData ->eventName) free( onData->eventName);		
}

void onDataRelease( eventHandler_closure_t onData) {
	  onData_t_release( (onData_t)onData, onDataDestructor );
}


/***************** Beginning of fire callbacks*********************/
int preFireCallback( eventName_t eventName,
                     eventHandler_closure_t onData,
                     eventHandler_closure_t fireData ) {
  fireData_t_ref( (fireData_t)fireData);
	return 0;
}

int postFireCallback( eventName_t eventName,
                      eventHandler_closure_t onData,
                      eventHandler_closure_t fireData ) {
  fireData_t_release( (fireData_t)fireData, NULL);
  return 0;
}

void allDoneCallBack( eventName_t eventName,
                      eventHandler_closure_t fireData ) {
 fireData_t_release( (fireData_t)fireData, NULL );
}


/********************* End of fire callbacks*********************/

/***********************Beginning of handlers*********************/


/* The following four handlers are for edit, browse, build, and home for part 6*/

void *handler_thread(void *onData) {
    onData_t onD = (onData_t)onData;
		pid_t pid;
		pid = fork();
		if (pid == 0) {
			execvp(onD->cmd,onD->args);
		} else {
			while(0==waitpid(pid,NULL,0)) usleep(10000);
      active_number_update(onD->eventName, -1);
		}
    return 0;
}


void edit_handler(eventHandler_closure_t onData, eventHandler_closure_t fireData) {
		onData_t onD = (onData_t)onData;
		pthread_t tid;
		pthread_create(&tid, NULL, handler_thread, onD); 

}


void browser_handler(eventHandler_closure_t onData, eventHandler_closure_t fireData) {
		onData_t onD = (onData_t)onData;
		pthread_t tid;
		pthread_create(&tid, NULL, handler_thread, onD); 

}

void build_handler(eventHandler_closure_t onData, eventHandler_closure_t fireData) {
		onData_t onD = (onData_t)onData;
		pthread_t tid;
		pthread_create(&tid, NULL, handler_thread, onD); 

}


void home_handler(eventHandler_closure_t onData, eventHandler_closure_t fireData){
		onData_t onD = (onData_t)onData;
		pthread_t tid;
		pthread_create(&tid, NULL, handler_thread, onD); 
}

/**************************end of handlers*************************/

// Registers events
int register_events(launchpad_data_t *data, eventManager_t manager) { 
    char * eventName = data->buttonLabel;
		onData_t onData;
		onData = onData_t_acquire();
		onData->eventName = eventName;
    onData->startDirectory = data->startDirectory;
    onData->cmd = data->cmd;
    onData->args = data->args;
		if (strcmp(eventName, "Edit") == 0) {
			eventManager_onEvent(manager, eventName, edit_handler, onData, onDataRelease);
		} else if (strcmp(eventName, "Browse") == 0) {
				eventManager_onEvent(manager, eventName, browser_handler, onData, onDataRelease);
		} else if (strcmp(eventName, "Build") == 0) {
				eventManager_onEvent(manager, eventName, build_handler, onData, onDataRelease);
		} else if (strcmp(eventName, "Home") == 0) {
				eventManager_onEvent(manager, eventName, home_handler, onData, onDataRelease);
		} else {
			return 1;
		}
		return 0;
}


/* Our new improved callback.  The data passed to this function
 * is printed to stdout. */
static void callback( GtkWidget *widget,
                      callbackData_t data)
{
		fireData_t fireData;
		fireData = fireData_t_acquire();
		fireData_t_ref(fireData);
    int remaining_value;
    remaining_value = *((int *)lookupHashItem(h, "launch_count"));  
    if (remaining_value > 0) {
      active_number_update(data->eventName, 1);
		  eventManager_fireEvent(data->manager, data->eventName, fireData, preFireCallback, postFireCallback, allDoneCallBack);
    }
}

/*** Hash Functions ****/

int
hashtable_hash_functon(void* k){
  int sum=0;
  int len = strlen((char *)k);
  int i;
  char *key = (char *) k;
  for(i=0; i < len; i++){
    sum =  sum + (int)key[i];
  }
  return sum;
}

int hashtable_key_comp_func(void* keyA, void* keyB){
	if (strcmp((char *)keyA,(char *)keyB)==0) return 0;
	return 1;
}

void destroy(GtkWidget *widget, gpointer data) {
	gtk_main_quit();
}

int main( int   argc,
          char *argv[] )
{
    /* GtkWidget is the storage type for widgets */
    GtkWidget *window;
    GtkWidget *button;
    GtkWidget *box1, *box2, *box3, *wrapper_box;
    GtkWidget *dir_label, *remaining_label,*remaining_count_label;

		
    int i;
    int button_count = getNButtons();
    launchpad_data_t** button_data = getButtons();
    char *label_string = getcwd (NULL,0);
    h = hashTable_create(button_count*2, hashtable_hash_functon, hashtable_key_comp_func);
    h_label = hashTable_create(button_count*2, hashtable_hash_functon, hashtable_key_comp_func);

		g_thread_init(NULL);
		gdk_threads_init();
		gdk_threads_enter();    
    /* This is called in all GTK applications. Arguments are parsed
     * from the command line and are returned to the application. */
    gtk_init (&argc, &argv);

    /* Create a new window */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
		g_signal_connect(window, "destroy", G_CALLBACK(destroy), NULL);
    /* This is a new call, which just sets the title of our
     * new window to "Hello Buttons!" */
    gtk_window_set_title (GTK_WINDOW (window), "launchPad");


    /* Sets the border width of the window. */
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);

    /* We create a box to pack widgets into.  This is described in detail
     * in the "packing" section. The box is not really visible, it
     * is just used as a tool to arrange widgets. */


    wrapper_box = gtk_vbox_new (FALSE, 0);
    box1 = gtk_hbox_new (FALSE, 0);
    box2 = gtk_hbox_new (FALSE, 0);
    box3 = gtk_hbox_new (FALSE, 0);
    /* Put the box into the main window. */
    
    gtk_container_add (GTK_CONTAINER (window), wrapper_box);    
    gtk_container_add (GTK_CONTAINER (wrapper_box), box1);
    gtk_container_add (GTK_CONTAINER (wrapper_box), box2);
    gtk_container_add (GTK_CONTAINER (wrapper_box), box3);

    dir_label = gtk_label_new (label_string);
    remaining_label = gtk_label_new(" launches remaining");

    char buffer[10]; 
	  sprintf( buffer, "%d", NWORKERS);
    remaining_count_label = gtk_label_new(buffer);


	  gtk_misc_set_alignment (GTK_MISC (dir_label), 0, 0);

	  gtk_box_pack_start (GTK_BOX (box1), dir_label, TRUE, TRUE, 0);    

    gtk_misc_set_alignment (GTK_MISC (remaining_count_label), 0, 0);

	  gtk_box_pack_start (GTK_BOX (box3), remaining_count_label, FALSE, FALSE, 0);        
    
	  gtk_misc_set_alignment (GTK_MISC (remaining_label), 0, 0);

	  gtk_box_pack_start (GTK_BOX (box3), remaining_label, TRUE, TRUE, 0);

    
    
    gtk_widget_show (dir_label);
    gtk_widget_show (remaining_label);
    gtk_widget_show (remaining_count_label);

		eventManager_t manager;
		eventManager_create(&manager, NWORKERS);

    for(i=0;i < button_count ; i++){
      char *key;
      int  *value;       
      //create temp_box
      GtkWidget *temp_box = gtk_vbox_new (TRUE, 0);
      //create temp_label
      GtkWidget *temp_label = gtk_label_new ("0 active");

      gtk_misc_set_alignment (GTK_MISC (temp_label), 0.5, 0);    
    
      gtk_widget_show (temp_label);
      

      //add temp_box to box2 
      gtk_container_add (GTK_CONTAINER (box2), temp_box);
      
      /* Creates a new button with the label. */
      button = gtk_button_new_with_label (button_data[i]->buttonLabel);

      /* Now when the button is clicked, we call the "callback" function
      * with a pointer to "button 1" as its argument */
//      g_signal_connect (G_OBJECT (button), "clicked",
//		      G_CALLBACK (callback), (gpointer) button_data[i]->buttonLabel);
	     
      // add button to temp_box
      gtk_box_pack_start (GTK_BOX(temp_box), button, FALSE, FALSE, 0);

      // add temp_label to temp_box
      gtk_box_pack_start (GTK_BOX (temp_box), temp_label, FALSE, FALSE, 0);
      
      key = (char*) malloc(sizeof(char)*strlen(button_data[i]->buttonLabel));
	    value = (int*) malloc(sizeof(int));      
      *value = 0;
      strcpy(key,button_data[i]->buttonLabel);
      
      addHashItem(h, key, value, free, free);
      addHashItem(h_label, button_data[i]->buttonLabel, temp_label, NULL, NULL);
      //show temp_box
      gtk_widget_show (temp_box);
      //show button
      gtk_widget_show (button);

      //Register events, initialize parameters for callback struct and pass it into callback function
			register_events(button_data[i], manager);

			callbackData_t callbackData;
			callbackData = callbackData_t_acquire();
			callbackData->eventName = button_data[i]->buttonLabel;
			callbackData->numberActive = *value;
			callbackData->manager = manager;
			callbackData->buttonData = *button_data[i];
			g_signal_connect (G_OBJECT (button), "clicked", 
				G_CALLBACK (callback), callbackData);      


    }
    int  *value;
	  value = (int*) malloc(sizeof(int));      
    *value = NWORKERS;
    addHashItem(h, "launch_count", value, NULL, free);
    addHashItem(h_label, "launch_count", remaining_count_label, NULL, NULL);      

    gtk_widget_show (button);
    gtk_widget_show (wrapper_box);
    gtk_widget_show (box1);
    gtk_widget_show (box2);
    gtk_widget_show (box3);
    gtk_widget_show (window);
 
		pthread_t pid;
		pthread_create(&pid, NULL, active_label_thread, NULL); 
    /* Rest in gtk_main and wait for the fun to begin! */
    gtk_main ();

		gdk_threads_leave();
    return 0;
}
